package com.example.pan.aria2;

import android.util.Log;

import androidx.annotation.NonNull;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class Aria2 {

    private Process mainProcess;
    private final Object processLock = new Object();
    private StreamWatcher errorWatcher;
    private StreamWatcher inputWatcher;
    private Env env;

    private Aria2() {

    }

    public static Aria2 getInstance() {
        return SingletonHolder.INSTANCE;
    }

    public boolean start() throws IOException {
        if (this.mainProcess != null) {
            return false;
        }
        if (this.env == null) {
            return false;
        }
        synchronized (this.processLock) {
            this.mainProcess = execWithParams(true, env.params());
            new Thread(this.inputWatcher = new StreamWatcher(this.mainProcess.getInputStream())).start();
            new Thread(this.inputWatcher = new StreamWatcher(this.mainProcess.getErrorStream())).start();
        }
        handleMessage(this.env.getExec() + Stream.of(env.params()).collect(Collectors.joining(" ", " ", "")));
        return true;
    }

    public void stop() {
        synchronized (this.processLock) {
            if (this.mainProcess != null) {
                this.mainProcess.destroy();
                this.mainProcess = null;
            }
        }
    }

    public void loadEnv(File exec, File downloadDir) throws IOException {
        this.env = new Env(exec, downloadDir);
    }

    private Process execWithParams(boolean redirect, String... params) throws IOException {
        if (this.env == null) {
            throw new RuntimeException("Env is null");
        }
        String[] cmd = new String[params.length + 1];
        cmd[0] = this.env.getExec();
        System.arraycopy(params, 0, cmd, 1, params.length);
        Process process = new ProcessBuilder(cmd).redirectErrorStream(redirect).start();
        if (process == null) {
            throw new IOException("Process is null!");
        }
        return process;
    }

    private void handleMessage(String line) {
        Log.e("Aria2", line);
    }

    public static class SingletonHolder {
        private static final Aria2 INSTANCE = new Aria2();
    }

    public static class Env {
        private final File exec;
        private final Map<String, String> params;

        public Env(File exec, File downloadDir) throws IOException {
            this.exec = exec;
            this.params = new HashMap<>();

            params.put("--daemon", "false");
            params.put("--enable-color", "false");
            params.put("--enable-rpc", "true");
            params.put("--dir", downloadDir.getCanonicalPath());
            params.put("--rpc-allow-origin-all", "true");
            params.put("--check-certificate", "false");
            params.put("--rpc-listen-all", "true");

            loadCustomOptions(params);
        }

        private void loadCustomOptions(Map<String, String> options) {

        }

        public String[] params() {
            return params.entrySet().stream().map(entry -> {
                if (entry.getValue() == null || entry.getValue().isEmpty()) {
                    return entry.getKey();
                }
                return entry.getKey() + "=" + entry.getValue();
            }).toArray(String[]::new);
        }

        public String getExec() {
            return exec.getAbsolutePath();
        }

        public Map<String, String> getParams() {
            return params;
        }
    }

    private class StreamWatcher implements Runnable, Closeable {
        private final InputStream stream;
        private volatile boolean shouldStop = false;

        StreamWatcher(@NonNull InputStream stream) {
            this.stream = stream;
        }

        @Override
        public void run() {
            try (Scanner scanner = new Scanner(stream)) {
                while (!shouldStop && scanner.hasNextLine()) {
                    String line = scanner.nextLine();
                    if (line.isEmpty()) {
                        continue;
                    }
                    handleMessage(line);
                }
            }
        }

        @Override
        public void close() {
            shouldStop = true;
        }
    }

}
